/*
===========================================================================

Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.

This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").

Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/

#include "Precompiled.h"
#include "globaldata.h"



#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "doomdef.h"
#include "doomstat.h"

#include "dstrings.h"
#include "sounds.h"


#include "z_zone.h"
#include "w_wad.h"
#include "s_sound.h"
#include "v_video.h"

#include "f_finale.h"
#include "f_wipe.h"

#include "m_argv.h"
#include "m_misc.h"
#include "m_menu.h"

#include "i_system.h"
#include "i_sound.h"
#include "i_video.h"

#include "g_game.h"

#include "hu_stuff.h"
#include "wi_stuff.h"
#include "st_stuff.h"
#include "am_map.h"

#include "p_setup.h"
#include "r_local.h"


#include "d_main.h"

//#include "precompiled.h"
//#include "../Main/PlayerProfile.h"
//#include "../Main/PSN/PS3_Session.h"
#include "d3xp/Game_local.h"

//
// D-DoomLoop()
// Not a globally visible function,
//  just included for source reference,
//  called by D_DoomMain, never exits.
// Manages timing and IO,
//  calls all ?_Responder, ?_Ticker, and ?_Drawer,
//  calls I_GetTime, I_StartFrame, and I_StartTic
//
void D_DoomLoop( void );

void R_ExecuteSetViewSize( void );
void D_CheckNetGame( void );
bool D_PollNetworkStart();
void D_ProcessEvents( void );
void D_DoAdvanceDemo( void );

const char*		wadfiles[MAXWADFILES] =
{
	0
};

const char*		extraWad = 0;

//
// EVENT HANDLING
//
// Events are asynchronous inputs generally generated by the game user.
// Events can be discarded if no responder claims them
//


//
// D_PostEvent
// Called by the I/O functions when input is detected
//
void D_PostEvent( event_t* ev )
{
	::g->events[::g->eventhead] = *ev;
	::g->eventhead = ( ++::g->eventhead ) & ( MAXEVENTS - 1 );
}


//
// D_ProcessEvents
// Send all the ::g->events of the given timestamp down the responder chain
//
void D_ProcessEvents( void )
{
	event_t*	ev;

	// IF STORE DEMO, DO NOT ACCEPT INPUT
	if( ( ::g->gamemode == commercial )
			&& ( W_CheckNumForName( "map01" ) < 0 ) )
	{
		return;
	}

	for( ; ::g->eventtail != ::g->eventhead ; ::g->eventtail = ( ++::g->eventtail ) & ( MAXEVENTS - 1 ) )
	{
		ev = &::g->events[::g->eventtail];
		if( M_Responder( ev ) )
		{
			continue;    // menu ate the event
		}
		G_Responder( ev );
	}
}




//
// D_Display
//  draw current display, possibly wiping it from the previous
//
// ::g->wipegamestate can be set to -1 to force a ::g->wipe on the next draw
extern bool waitingForWipe;

void D_Wipe()
{
	int nowtime, tics;

	nowtime = I_GetTime();
	tics = nowtime - ::g->wipestart;

	if( tics != 0 )
	{
		::g->wipestart = nowtime;
		::g->wipedone = wipe_ScreenWipe( 0, 0, SCREENWIDTH, SCREENHEIGHT, tics );

		// DHM - Nerve :: Demo recording :: Stop large hitch on first frame after the wipe
		if( ::g->wipedone )
		{
			::g->oldtrt_entertics = nowtime / ::g->ticdup;
			::g->gametime = nowtime;
			::g->wipe = false;
			waitingForWipe = false;
		}
	}
}


void D_Display( void )
{
	qboolean			redrawsbar;

	if( ::g->nodrawers )
	{
		return;    // for comparative timing / profiling
	}

	redrawsbar = false;

	// change the view size if needed
	if( ::g->setsizeneeded )
	{
		R_ExecuteSetViewSize();
		::g->oldgamestate = ( gamestate_t ) - 1;                  // force background redraw
		::g->borderdrawcount = 3;
	}

	// save the current screen if about to ::g->wipe
	if( ::g->gamestate != ::g->wipegamestate )
	{
		::g->wipe = true;
		wipe_StartScreen( 0, 0, SCREENWIDTH, SCREENHEIGHT );
	}
	else
	{
		::g->wipe = false;
	}

	if( ::g->gamestate == GS_LEVEL && ::g->gametic )
	{
		HU_Erase();
	}

	// do buffered drawing
	switch( ::g->gamestate )
	{
		case GS_LEVEL:
			if( !::g->gametic )
			{
				break;
			}
			if( ::g->automapactive )
			{
				AM_Drawer();
			}
			if( ::g->wipe || ( ::g->viewheight != 200 * GLOBAL_IMAGE_SCALER && ::g->fullscreen ) )
			{
				redrawsbar = true;
			}
			if( ::g->inhelpscreensstate && !::g->inhelpscreens )
			{
				redrawsbar = true;    // just put away the help screen
			}
			ST_Drawer( ::g->viewheight == 200 * GLOBAL_IMAGE_SCALER, redrawsbar );
			::g->fullscreen = ::g->viewheight == 200 * GLOBAL_IMAGE_SCALER;
			break;

		case GS_INTERMISSION:
			WI_Drawer();
			break;

		case GS_FINALE:
			F_Drawer();
			break;

		case GS_DEMOSCREEN:
			D_PageDrawer();
			break;
	}

	// draw buffered stuff to screen
	I_UpdateNoBlit();

	// draw the view directly
	if( ::g->gamestate == GS_LEVEL && !::g->automapactive && ::g->gametic )
	{
		R_RenderPlayerView( &::g->players[::g->displayplayer] );
	}

	if( ::g->gamestate == GS_LEVEL && ::g->gametic )
	{
		HU_Drawer();
	}

	// clean up border stuff
	if( ::g->gamestate != ::g->oldgamestate && ::g->gamestate != GS_LEVEL )
	{
		I_SetPalette( ( byte* )W_CacheLumpName( "PLAYPAL", PU_CACHE_SHARED ) );
	}

	// see if the border needs to be initially drawn
	if( ::g->gamestate == GS_LEVEL && ::g->oldgamestate != GS_LEVEL )
	{
		::g->viewactivestate = false;        // view was not active
		R_FillBackScreen();     // draw the pattern into the back screen
	}

	// see if the border needs to be updated to the screen
	if( ::g->gamestate == GS_LEVEL && !::g->automapactive && ::g->scaledviewwidth != ( 320 * GLOBAL_IMAGE_SCALER ) )
	{
		if( ::g->menuactive || ::g->menuactivestate || !::g->viewactivestate )
		{
			::g->borderdrawcount = 3;
		}
		if( ::g->borderdrawcount )
		{
			R_DrawViewBorder();     // erase old menu stuff
			::g->borderdrawcount--;
		}

	}

	::g->menuactivestate = ::g->menuactive;
	::g->viewactivestate = ::g->viewactive;
	::g->inhelpscreensstate = ::g->inhelpscreens;
	::g->oldgamestate = ::g->wipegamestate = ::g->gamestate;

	// draw pause pic
	/*
		if (::g->paused)
		{
			if (::g->automapactive)
				y = 4;
			else
				y = ::g->viewwindowy+4;
			V_DrawPatchDirect(::g->viewwindowx+(ORIGINAL_WIDTH-68)/2,
				y,0,(patch_t*)W_CacheLumpName ("M_PAUSE", PU_CACHE_SHARED));
		}
	*/

	// menus go directly to the screen
	M_Drawer();           // menu is drawn even on top of everything
	NetUpdate( NULL );          // send out any new accumulation

	// normal update
	if( !::g->wipe )
	{
		I_FinishUpdate();               // page flip or blit buffer
		return;
	}

	// \ update
	wipe_EndScreen( 0, 0, SCREENWIDTH, SCREENHEIGHT );

	::g->wipestart = I_GetTime() - 1;

	D_Wipe(); // initialize g->wipedone
}



void D_RunFrame( bool Sounds )
{
	if( Sounds )
	{
		// move positional sounds
		S_UpdateSounds( ::g->players[::g->consoleplayer].mo );
	}

	// Update display, next frame, with current state.
	D_Display();

	if( Sounds )
	{
		// Update sound output.
		I_SubmitSound();
	}
}



//
//  D_DoomLoop
//
void D_DoomLoop( void )
{
	// DHM - Not used
	/*
		if (M_CheckParm ("-debugfile"))
		{
			char    filename[20];
			sprintf (filename,"debug%i.txt",::g->consoleplayer);
			I_Printf ("debug output to: %s\n",filename);
			::g->debugfile = f o p e n(filename,"w");
		}

		I_InitGraphics ();

		while (1)
		{
			TryRunTics();
			D_RunFrame( true );
		}
	*/
}



//
//  DEMO LOOP
//


//
// D_PageTicker
// Handles timing for warped ::g->projection
//
void D_PageTicker( void )
{
	if( --::g->pagetic < 0 )
	{
		D_AdvanceDemo();
	}
}



//
// D_PageDrawer
//
void D_PageDrawer( void )
{
	V_DrawPatch( 0, 0, 0, ( patch_t* )W_CacheLumpName( ::g->pagename, PU_CACHE_SHARED ) );
}


//
// D_AdvanceDemo
// Called after each demo or intro ::g->demosequence finishes
//
void D_AdvanceDemo( void )
{
	::g->advancedemo = true;
}


//
// This cycles through the demo sequences.
// FIXME - version dependend demo numbers?
//
void D_DoAdvanceDemo( void )
{
	::g->players[::g->consoleplayer].playerstate = PST_LIVE;  // not reborn
	::g->advancedemo = false;
	::g->usergame = false;               // no save / end game here
	::g->paused = false;
	::g->gameaction = ga_nothing;

	if( ::g->gamemode == retail )
	{
		::g->demosequence = ( ::g->demosequence + 1 ) % 8;
	}
	else
	{
		::g->demosequence = ( ::g->demosequence + 1 ) % 6;
	}

	switch( ::g->demosequence )
	{
		case 0:
			if( ::g->gamemode == commercial )
			{
				::g->pagetic = 35 * 11;
			}
			else
			{
				::g->pagetic = 8 * TICRATE;
			}

			::g->gamestate = GS_DEMOSCREEN;
			::g->pagename = ( char* )"INTERPIC";

			if( ::g->gamemode == commercial )
			{
				S_StartMusic( mus_dm2ttl );
			}
			else
			{
				S_StartMusic( mus_intro );
			}

			break;
		case 1:
			G_DeferedPlayDemo( "demo1" );
			break;
		case 2:
			::g->pagetic = 3 * TICRATE;
			::g->gamestate = GS_DEMOSCREEN;
			::g->pagename = ( char* )"INTERPIC";
			break;
		case 3:
			G_DeferedPlayDemo( "demo2" );
			break;
		case 4:
			::g->pagetic = 3 * TICRATE;
			::g->gamestate = GS_DEMOSCREEN;
			::g->pagename = ( char* )"INTERPIC";
			break;
		case 5:
			G_DeferedPlayDemo( "demo3" );
			break;
		// THE DEFINITIVE DOOM Special Edition demo
		case 6:
			::g->pagetic = 3 * TICRATE;
			::g->gamestate = GS_DEMOSCREEN;
			::g->pagename = ( char* )"INTERPIC";
			break;
		case 7:
			G_DeferedPlayDemo( "demo4" );
			break;
	}
}



//
// D_StartTitle
//
void D_StartTitle( void )
{
	::g->gameaction = ga_nothing;
	::g->demosequence = -1;
	D_AdvanceDemo();
}




//      print ::g->title for every printed line

//
// D_AddExtraWadFile
//
void D_SetExtraWadFile( const char* file )
{
	extraWad = file;
}

//
// D_AddFile
//
void D_AddFile( const char* file )
{
	int     numwadfiles;

	for( numwadfiles = 0 ; wadfiles[numwadfiles] ; numwadfiles++ )
		if( file == wadfiles[numwadfiles] )
		{
			return;
		}
	;
	wadfiles[numwadfiles] = file;
}

//
// IdentifyVersion
// Checks availability of IWAD files by name,
// to determine whether registered/commercial features
// should be executed (notably loading PWAD's).
//

void IdentifyVersion( void )
{
	W_FreeWadFiles();

	const ExpansionData* expansion =  DoomLib::GetCurrentExpansion();
	::g->gamemode = expansion->gameMode;
	::g->gamemission = expansion->pack_type;


	if( expansion->type == ExpansionData::PWAD )
	{
		D_AddFile( expansion->iWadFilename );
		D_AddFile( expansion->pWadFilename );

	}
	else
	{
		D_AddFile( expansion->iWadFilename );
	}

}


//
// Find a Response File
//
void FindResponseFile( void )
{
}


//
// D_DoomMain
//

void D_DoomMain( void )
{
	int             p;
	char                    file[256];


	FindResponseFile();

	IdentifyVersion();

	setbuf( stdout, NULL );
	::g->modifiedgame = false;

	// TODO: Networking
	//const bool isDeathmatch = gameLocal->GetMatchParms().GetGameType() == GAME_TYPE_PVP;
	const bool isDeathmatch = false;

	::g->nomonsters = M_CheckParm( "-nomonsters" ) || isDeathmatch;
	::g->respawnparm = M_CheckParm( "-respawn" );
	::g->fastparm = M_CheckParm( "-fast" );
	::g->devparm = M_CheckParm( "-devparm" );
	if( M_CheckParm( "-altdeath" ) || isDeathmatch )
	{
		::g->deathmatch = 2;
	}
	else if( M_CheckParm( "-deathmatch" ) )
	{
		::g->deathmatch = 1;
	}

	switch( ::g->gamemode )
	{
		case retail:
			idStr::snPrintf( ::g->title, sizeof( ::g->title ),
							 "                         "
							 "The Ultimate DOOM Startup v%i.%i"
							 "                           ",
							 VERSION / 100, VERSION % 100 );
			break;
		case shareware:
			idStr::snPrintf( ::g->title, sizeof( ::g->title ),
							 "                            "
							 "DOOM Shareware Startup v%i.%i"
							 "                           ",
							 VERSION / 100, VERSION % 100 );
			break;
		case registered:
			idStr::snPrintf( ::g->title, sizeof( ::g->title ),
							 "                            "
							 "DOOM Registered Startup v%i.%i"
							 "                           ",
							 VERSION / 100, VERSION % 100 );
			break;
		case commercial:
			idStr::snPrintf( ::g->title, sizeof( ::g->title ),
							 "                         "
							 "DOOM 2: Hell on Earth v%i.%i"
							 "                           ",
							 VERSION / 100, VERSION % 100 );
			break;
		default:
			idStr::snPrintf( ::g->title, sizeof( ::g->title ),
							 "                     "
							 "Public DOOM - v%i.%i"
							 "                           ",
							 VERSION / 100, VERSION % 100 );
			break;
	}

	I_Printf( "%s\n", ::g->title );

	if( ::g->devparm )
	{
		I_Printf( D_DEVSTR );
	}

	if( M_CheckParm( "-cdrom" ) )
	{
		I_Printf( D_CDROM );
//c++		mkdir("c:\\doomdata",0);
		strcpy( ::g->basedefault, "c:/doomdata/default.cfg" );
	}

	// add any files specified on the command line with -file ::g->wadfile
	// to the wad list
	//
	p = M_CheckParm( "-file" );
	if( p )
	{
		// the parms after p are ::g->wadfile/lump names,
		// until end of parms or another - preceded parm
		::g->modifiedgame = true;            // homebrew levels
		while( ++p != ::g->myargc && ::g->myargv[p][0] != '-' )
		{
			D_AddFile( ::g->myargv[p] );
		}
	}

	p = M_CheckParm( "-playdemo" );

	if( !p )
	{
		p = M_CheckParm( "-timedemo" );
	}

	if( p && p < ::g->myargc - 1 )
	{
		idStr::snPrintf( file, sizeof( file ), "d:\\%s.lmp", ::g->myargv[p + 1] );
		D_AddFile( file );
		I_Printf( "Playing demo %s.lmp.\n", ::g->myargv[p + 1] );
	}

	// get skill / episode / map from defaults
	::g->startskill = sk_medium;
	::g->startepisode = 1;
	::g->startmap = 1;
	::g->autostart = false;

	if( DoomLib::matchParms.gameEpisode != GAME_EPISODE_UNKNOWN )
	{
		::g->startepisode = DoomLib::matchParms.gameEpisode;
		::g->autostart = 1;
	}

	if( DoomLib::matchParms.gameMap != -1 )
	{
		::g->startmap = DoomLib::matchParms.gameMap;
		::g->autostart = 1;
	}

	if( DoomLib::matchParms.gameSkill != -1 )
	{
		::g->startskill = ( skill_t )DoomLib::matchParms.gameSkill;
	}

	// get skill / episode / map from cmdline
	p = M_CheckParm( "-skill" );
	if( p && p < ::g->myargc - 1 )
	{
		::g->startskill = ( skill_t )( ::g->myargv[p + 1][0] - '1' );
		::g->autostart = true;
	}

	p = M_CheckParm( "-episode" );
	if( p && p < ::g->myargc - 1 )
	{
		::g->startepisode = ::g->myargv[p + 1][0] - '0';
		::g->startmap = 1;
		::g->autostart = true;
	}

	/*p = M_CheckParm ("-timer");
	if (p && p < ::g->myargc-1 && ::g->deathmatch)
	{*/
	// TODO: Networking
	//const int timeLimit = gameLocal->GetMatchParms().GetTimeLimit();
	const int timeLimit = 0;
	if( timeLimit != 0 && ::g->deathmatch )
	{
		int     time;
		//time = atoi(::g->myargv[p+1]);
		time = timeLimit;
		I_Printf( "Levels will end after %d minute", time );
		if( time > 1 )
		{
			I_Printf( "s" );
		}
		I_Printf( ".\n" );
	}

	p = M_CheckParm( "-avg" );
	if( p && p < ::g->myargc - 1 && ::g->deathmatch )
	{
		I_Printf( "Austin Virtual Gaming: Levels will end after 20 minutes\n" );
	}

	p = M_CheckParm( "-warp" );
	if( p && p < ::g->myargc - 1 )
	{
		if( ::g->gamemode == commercial )
		{
			::g->startmap = atoi( ::g->myargv[p + 1] );
		}
		else
		{
			::g->startepisode = ::g->myargv[p + 1][0] - '0';
			::g->startmap = ::g->myargv[p + 2][0] - '0';
		}
		::g->autostart = true;
	}

	I_Printf( "Z_Init: Init zone memory allocation daemon. \n" );
	Z_Init();

	// init subsystems
	I_Printf( "V_Init: allocate ::g->screens.\n" );
	V_Init();

	I_Printf( "M_LoadDefaults: Load system defaults.\n" );
	M_LoadDefaults();               // load before initing other systems

	I_Printf( "W_Init: Init WADfiles.\n" );
	W_InitMultipleFiles( wadfiles );


	// Check for -file in shareware
	if( ::g->modifiedgame )
	{
		// These are the lumps that will be checked in IWAD,
		// if any one is not present, execution will be aborted.
		char name[23][16] =
		{
			"e2m1", "e2m2", "e2m3", "e2m4", "e2m5", "e2m6", "e2m7", "e2m8", "e2m9",
			"e3m1", "e3m3", "e3m3", "e3m4", "e3m5", "e3m6", "e3m7", "e3m8", "e3m9",
			"dphoof", "bfgga0", "heada1", "cybra1", "spida1d1"
		};
		int i;

		if( ::g->gamemode == shareware )
			I_Error( "\nYou cannot -file with the shareware "
					 "version. Register!" );

		// Check for fake IWAD with right name,
		// but w/o all the lumps of the registered version.
		if( ::g->gamemode == registered )
			for( i = 0; i < 23; i++ )
				if( W_CheckNumForName( name[i] ) < 0 )
				{
					I_Error( "\nThis is not the registered version." );
				}
	}

	// Iff additonal PWAD files are used, print modified banner
	if( ::g->modifiedgame )
	{
		/*m*/I_Printf(
			"===========================================================================\n"
			"ATTENTION:  This version of DOOM has been modified.  If you would like to\n"
			"get a copy of the original game, call 1-800-IDGAMES or see the readme file.\n"
			"        You will not receive technical support for modified games.\n"
			"                      press enter to continue\n"
			"===========================================================================\n"
		);
		getchar();
	}


	// Check and print which version is executed.
	switch( ::g->gamemode )
	{
		case shareware:
		case indetermined:
			I_Printf(
				"===========================================================================\n"
				"                                Shareware!\n"
				"===========================================================================\n"
			);
			break;
		case registered:
		case retail:
		case commercial:
			I_Printf(
				"===========================================================================\n"
				"                 Commercial product - do not distribute!\n"
				"         Please report software piracy to the SPA: 1-800-388-PIR8\n"
				"===========================================================================\n"
			);
			break;

		default:
			// Ouch.
			break;
	}

	I_Printf( "M_Init: Init miscellaneous info.\n" );
	M_Init();

	I_Printf( "R_Init: Init DOOM refresh daemon - " );
	R_Init();

	I_Printf( "\nP_Init: Init Playloop state.\n" );
	P_Init();

	I_Printf( "I_Init: Setting up machine state.\n" );
	I_Init();

	I_Printf( "D_CheckNetGame: Checking network game status.\n" );
	D_CheckNetGame();
}

bool D_DoomMainPoll( void )
{
	int             p;
	char                    file[256];

	if( D_PollNetworkStart() == false )
	{
		return false;
	}


	I_Printf( "S_Init: Setting up sound.\n" );
	S_Init( s_volume_sound.GetInteger(), s_volume_midi.GetInteger() );

	I_Printf( "HU_Init: Setting up heads up display.\n" );
	HU_Init();

	I_Printf( "ST_Init: Init status bar.\n" );
	ST_Init();

	// start the apropriate game based on parms
	p = M_CheckParm( "-record" );

	if( p && p < ::g->myargc - 1 )
	{
		G_RecordDemo( ::g->myargv[p + 1] );
		::g->autostart = true;
	}

	p = M_CheckParm( "-playdemo" );
	if( p && p < ::g->myargc - 1 )
	{
		//::g->singledemo = true;              // quit after one demo
		G_DeferedPlayDemo( ::g->myargv[p + 1] );
		//D_DoomLoop ();  // never returns
	}

	p = M_CheckParm( "-timedemo" );
	if( p && p < ::g->myargc - 1 )
	{
		G_TimeDemo( "nukage1" ); //::g->myargv[p+1]);
		D_DoomLoop();   // never returns
	}

	p = M_CheckParm( "-loadgame" );
	if( p && p < ::g->myargc - 1 )
	{
		if( M_CheckParm( "-cdrom" ) )
		{
			idStr::snPrintf( file, sizeof( file ), "c:\\doomdata\\" SAVEGAMENAME "%c.dsg", ::g->myargv[p + 1][0] );
		}
		else
		{
			idStr::snPrintf( file, sizeof( file ), SAVEGAMENAME "%c.dsg", ::g->myargv[p + 1][0] );
		}
		G_LoadGame( file );
	}


	if( ::g->gameaction != ga_loadgame && ::g->gameaction != ga_playdemo )
	{
		if( ::g->autostart || ::g->netgame )
		{
			G_InitNew( ::g->startskill, ::g->startepisode, ::g->startmap );
		}
		else if( ::g->gameaction != ga_newgame )
		{
			D_StartTitle();                 // start up intro loop
		}
	}
	return true;
}


